// -----------------------------------------------------------------------------
// ExcalidrawImageElement & related helpers
// -----------------------------------------------------------------------------

import { MIME_TYPES, SVG_NS } from '../constants'
import { t } from  '/imports/i18n'
import { AppClassProperties, DataURL, BinaryFiles } from '../types'
import { isInitializedImageElement } from './typeChecks'
import {
  ExcalidrawElement,
  FileId,
  InitializedExcalidrawImageElement,
} from './types'

export const loadHTMLImageElement = (dataURL: DataURL) => {
  return new Promise<HTMLImageElement>((resolve, reject) => {
    const image = new Image()
    image.onload = () => {
      resolve(image)
    }
    image.onerror = (error) => {
      reject(error)
    }
    image.src = dataURL
  })
}

/** NOTE: updates cache even if already populated with given image. Thus,
 * you should filter out the images upstream if you want to optimize this. */
export const updateImageCache = async ({
  fileIds,
  files,
  imageCache,
}: {
  fileIds: FileId[]
  files: BinaryFiles
  imageCache: AppClassProperties['imageCache']
}) => {
  const updatedFiles = new Map<FileId, true>()
  const erroredFiles = new Map<FileId, true>()

  await Promise.all(
    fileIds.reduce((promises, fileId) => {
      const fileData = files[fileId as string]
      if (fileData && !updatedFiles.has(fileId)) {
        updatedFiles.set(fileId, true)
        return promises.concat(
          (async () => {
            try {
              if (fileData.mimeType === MIME_TYPES.binary) {
                throw new Error('Only images can be added to ImageCache')
              }

              const imagePromise = loadHTMLImageElement(fileData.dataURL)
              const data = {
                image: imagePromise,
                mimeType: fileData.mimeType,
              } as const
              // store the promise immediately to indicate there's an in-progress
              // initialization
              imageCache.set(fileId, data)

              const image = await imagePromise

              imageCache.set(fileId, { ...data, image })
            } catch (error: any) {
              erroredFiles.set(fileId, true)
            }
          })(),
        )
      }
      return promises
    }, [] as Promise<any>[]),
  )

  return {
    imageCache,
    /** includes errored files because they cache was updated nonetheless */
    updatedFiles,
    /** files that failed when creating HTMLImageElement */
    erroredFiles,
  }
}

export const getInitializedImageElements = (
  elements: readonly ExcalidrawElement[],
) =>
  elements.filter((element) =>
    isInitializedImageElement(element),
  ) as InitializedExcalidrawImageElement[]

export const isHTMLSVGElement = (node: Node | null): node is SVGElement => {
  // lower-casing due to XML/HTML convention differences
  // https://johnresig.com/blog/nodename-case-sensitivity
  return node?.nodeName.toLowerCase() === 'svg'
}

export const normalizeSVG = async (SVGString: string) => {
  const doc = new DOMParser().parseFromString(SVGString, MIME_TYPES.svg)
  const svg = doc.querySelector('svg')
  const errorNode = doc.querySelector('parsererror')
  if (errorNode || !isHTMLSVGElement(svg)) {
    throw new Error(t('errors.invalidSVGString'))
  } else {
    if (!svg.hasAttribute('xmlns')) {
      svg.setAttribute('xmlns', SVG_NS)
    }

    if (!svg.hasAttribute('width') || !svg.hasAttribute('height')) {
      const viewBox = svg.getAttribute('viewBox')
      let width = svg.getAttribute('width') || '50'
      let height = svg.getAttribute('height') || '50'
      if (viewBox) {
        const match = viewBox.match(/\d+ +\d+ +(\d+) +(\d+)/)
        if (match) {
          [, width, height] = match
        }
      }
      svg.setAttribute('width', width)
      svg.setAttribute('height', height)
    }

    return svg.outerHTML
  }
}
